// Copyright (c) 2010 god6or@gmail.com under MIT license.

package ul.comm

import java.io.{InputStream, OutputStream}
import gnu.io.{CommPortIdentifier, SerialPort}
import scala.collection.mutable.{ArrayBuffer}



class Serial extends SerialBase {
    
    /// returns list of available ports names
    def ports:List[String] = {
        val pns = new ArrayBuffer[String]
        val pIds = CommPortIdentifier.getPortIdentifiers()
        while ( pIds.hasMoreElements() ) {
            val pId = pIds.nextElement().asInstanceOf[CommPortIdentifier]
            if ( pId.getPortType() == CommPortIdentifier.PORT_SERIAL ) pns += pId.getName()
        }
        pns.sorted.toList
    }

    /// port ID
    var portId:CommPortIdentifier = null
    /// port object
    var port:SerialPort = null
    /// port input stream
    var in:InputStream = null
    /// port output stream
    var out:OutputStream = null
    
    /// opens port by name
    def open( name:String, bufSize:Int = 8192 ):Boolean = {
        var opened = false
        val pIds = CommPortIdentifier.getPortIdentifiers()
        while ( pIds.hasMoreElements() ) {
            val pId = pIds.nextElement().asInstanceOf[CommPortIdentifier]
            if ( pId.getPortType() == CommPortIdentifier.PORT_SERIAL && pId.getName() == name) {
                this.name = name
                portId = pId
                port = pId.open("SimpleWrite", bufSize).asInstanceOf[SerialPort]
                port.disableReceiveFraming()
                port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE)
                timeoutRX = 1000
                in = port.getInputStream()
                out = port.getOutputStream()
                opened = true
            }
        }
        opened
    }
    /// closes port
    def close = {
        if (this.name != null) {
            port.close()
            name = null
            portId = null
        }
    }

    /// get/set RX timeout (milliseconds)
    def timeoutRX:Int = if (port.isReceiveTimeoutEnabled()) port.getReceiveTimeout() else -1
    def timeoutRX_=(timeout:Int) = {
        port.enableReceiveTimeout(timeout)
    }
    
    /// get/set size of RX/TX buffers
    def bufRX = port.getInputBufferSize()
    def bufRX_=(size:Int) = port.setInputBufferSize(size)
    def bufTX = port.getOutputBufferSize()
    def bufTX_=(size:Int) = port.setOutputBufferSize(size)
    
    /// set port configuration
    def setup( baud:Int, bits:Int, stops:Double, parity:String ) = {
        this.baud = baud
        this.bits = bits
        this.stops = stops
        this.parity = parity
    }
    
    /// get/set baud rate
    def baud:Int = port.getBaudRate()
    def baud_=(baud:Int) = {
        port.setSerialPortParams(baud, port.getDataBits(), port.getStopBits(), port.getParity());
    }
    
    /// get/set data bits
    def bits:Int = port.getDataBits() match {
        case SerialPort.DATABITS_5 => 5
        case SerialPort.DATABITS_6 => 6
        case SerialPort.DATABITS_7 => 7
        case SerialPort.DATABITS_8 => 8
    }
    def bits_=(b:Int) = {
        port.setSerialPortParams(port.getBaudRate(), b match {
            case 5 => SerialPort.DATABITS_5
            case 6 => SerialPort.DATABITS_6
            case 7 => SerialPort.DATABITS_7
            case 8 => SerialPort.DATABITS_8
            case _ => SerialPort.DATABITS_8
        }, port.getStopBits(), port.getParity());
    }

    /// get/set stop bits
    def stops:Double = port.getStopBits() match {
        case SerialPort.STOPBITS_1 => 1.0
        case SerialPort.STOPBITS_1_5 => 1.5
        case SerialPort.STOPBITS_2 => 2.0
    }
    def stops_=(s:Double) = {
        port.setSerialPortParams(port.getBaudRate(), port.getDataBits(), s match {
            case 1.0 => SerialPort.STOPBITS_1
            case 1.5 => SerialPort.STOPBITS_1_5
            case 2.0 => SerialPort.STOPBITS_2
            case _ => SerialPort.STOPBITS_1
        }, port.getParity());
    }

    /// get/set parity
    def parity:String = port.getParity() match {
        case SerialPort.PARITY_NONE  => "n"
        case SerialPort.PARITY_ODD   => "o"
        case SerialPort.PARITY_EVEN  => "e"
        case SerialPort.PARITY_MARK  => "m"
        case SerialPort.PARITY_SPACE => "s"
    }
    def parity_=(p:String) = {
        port.setSerialPortParams(port.getBaudRate(), port.getDataBits(), port.getStopBits(), p.substring(0,1).toLowerCase() match {
            case "n" => SerialPort.PARITY_NONE
            case "o" => SerialPort.PARITY_ODD
            case "e" => SerialPort.PARITY_EVEN
            case "m" => SerialPort.PARITY_MARK
            case "s" => SerialPort.PARITY_SPACE
            case  _  => SerialPort.PARITY_NONE
        });
    }
    
    /// get/set port signals
    def DTR:Boolean = port.isDTR()
    def DTR_=(state:Boolean) = port.setDTR(state)
    def RTS:Boolean = port.isRTS()
    def RTS_=(state:Boolean) = port.setRTS(state)
    def CTS:Boolean = port.isCTS()
    def DCD:Boolean = port.isCD()
    def RI:Boolean = port.isRI()

    /// returns number of available bytes in RX buffer
    def availRX = in.available()
    /// reads n bytes from RX buffer to array (available number if n == -1)
    def read(n:Int = -1):Array[Byte] = {
        val rdArray = new ArrayBuffer[Byte]
        val bc:Int = if (n == -1) availRX else n
        var rdByte:Int = 0
        for (i <- 1 to bc if rdByte >= 0) {
            rdByte = in.read()
            if (rdByte > -1) rdArray += rdByte.toByte
        }
        rdArray.toArray
    }
    /// writes bytes
    def write( bb:Seq[Byte] ) = {
        for ( b <- bb ) this.out.write(b)
    }
    
    /// flush RX/TX buffers
    def flushRX = this.in.skip( this.in.available() )
    def flushTX = this.out.flush()
}



object Serial extends Serial {
    /// opens port by name
    def apply( name:String ):Serial = {
        val p = new Serial
        if (p.open(name)) p else null
    }
    /// opens port by number (first is 0)
    def apply( num:Int ):Serial = {
        val p = new Serial
        if (p.open( ports( num ) )) p else null
    }
}

